<template>
  <div class="custom-process-designer">
    <div class="custom-process-designer__header">
      <el-button-group key="file-control" style="display: flex;">
        <el-tooltip effect="dark" content="保存">
          <el-button size="mini" @click="processSave()">
            <i class="el-icon-document" /> 保存并发布
          </el-button>
        </el-tooltip>
        <el-upload
          :file-list="fileList"
          action=""
          :auto-upload="false"
          :show-file-list="false"
          :on-change="handleOpenFile">
          <el-tooltip class="item" effect="dark" content="导入本地BPMN文件" placement="bottom">
            <el-button icon="el-icon-folder-opened" size="mini" />
          </el-tooltip>
        </el-upload>
        <el-tooltip effect="dark" content="重新绘制">
          <el-button size="mini" icon="el-icon-plus" @click="processRestart()" />
        </el-tooltip>
        <el-tooltip effect="dark">
          <div slot="content">
            <el-button type="text" @click="downloadProcessAsXml()">下载为XML文件</el-button>
            <br>
            <el-button type="text" @click="downloadProcessAsSvg()">下载为SVG文件</el-button>
            <br>
            <el-button type="text" @click="downloadProcessAsBpmn()">下载为BPMN文件</el-button>
          </div>
          <el-tooltip class="item" effect="dark" content="下载文件" placement="bottom">
            <el-button icon="el-icon-download" size="mini" />
          </el-tooltip>
        </el-tooltip>

        <el-tooltip effect="dark" :content="simulationStatus ? '退出模拟' : '开启模拟'">
          <el-button icon="el-icon-cpu" size="mini" @click="processSimulation" />
        </el-tooltip>
        <el-tooltip effect="dark" content="缩小视图">
          <el-button size="mini" :disabled="defaultZoom < 0.2" icon="el-icon-zoom-out" @click="processZoomOut()" />
        </el-tooltip>
        <el-button size="mini">{{ Math.floor(defaultZoom * 10 * 10) + "%" }}</el-button>
        <el-tooltip effect="dark" content="放大视图">
          <el-button size="mini" :disabled="defaultZoom > 4" icon="el-icon-zoom-in" @click="processZoomIn()" />
        </el-tooltip>
        <el-tooltip effect="dark" content="重置视图并居中">
          <el-button size="mini" icon="el-icon-c-scale-to-original" @click="processReZoom()" />
        </el-tooltip>
        <el-tooltip effect="dark" content="撤销">
          <el-button size="mini" :disabled="!revocable" icon="el-icon-refresh-left" @click="processUndo()" />
        </el-tooltip>
        <el-tooltip effect="dark" content="恢复">
          <el-button size="mini" :disabled="!recoverable" icon="el-icon-refresh-right" @click="processRedo()" />
        </el-tooltip>

      </el-button-group>
    </div>
    <div class="custom-process-designer__container">
      <el-tabs v-model="activeName" type="border-card" class="custom-process-tabs">
        <el-tab-pane label="设计器" name="designer" />
        <el-tab-pane label="XML" name="xml" />
        <el-tab-pane label="JSON" name="json" />
      </el-tabs>
      <div v-show="activeName ==='designer' " style="height:100%">
        <div style=" display: flex;flex-direction: row;height:100%">
          <div ref="bpmn-canvas" class="custom-process-designer__canvas" />
          <div class="custom-process-panel">
            <custom-properties-panel :key="`penal-${reloadIndex}`" :bpmn-modeler="bpmnModeler" :prefix="'activiti'" class="process-panel" />
          </div>
        </div>
      </div>
      <div v-if="activeName ==='xml' " style="height:100%;overflow: auto;padding: 10px 20px;line-height: 30px;">
        <pre><code v-html="previewXMLResult" /></pre>
      </div>
      <div v-if="activeName ==='json' " style="height:100%;overflow: auto;">
        <json-viewer
          :value="previewJSONResult"
          :expand-depth="15"
          copyable
          sort />
      </div>
    </div>
  </div>
</template>
<script>
import BpmnModeler from 'bpmn-js/lib/Modeler'
import DefaultEmptyXML from './plugins/defaultEmpty'

// 翻译方法
import customTranslate from './plugins/translate/customTranslate'
import translationsCN from './plugins/translate/zh'

// 模型描述
import activitiModdleDescriptor from './plugins/descriptor/activitiDescriptor.json'

import convert from 'xml-js'
import JsonViewer from 'vue-json-viewer'

import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-dark-reasonable.css'

import CustomPropertiesPanel from './refactor/PropertiesPanel'
export default {
  components: { JsonViewer, CustomPropertiesPanel },
  props: {
    translations: {
      type: Object,
      default: () => {
        return null
      }
    },
    moddleExtension: {
      type: Object,
      default: () => {
        return null
      }
    },
    events: {
      type: Array,
      default: () => ['element.click']
    },
    onlyCustomizeModdle: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      bpmnModeler: null,
      activeName: 'designer',
      prefix: 'activiti',
      defaultZoom: 1,
      previewModelVisible: false,
      simulationStatus: false,
      previewJSONResult: {},
      previewXMLResult: '',
      previewType: 'xml',
      recoverable: false,
      revocable: false,

      reloadIndex: 0,

      fileList: [],
      workFlowDetails: {}
    }
  },
  computed: {

    /**
     * 加载模块
     */
    additionalModules() {
      const Modules = []
      /**
       * 翻译
       */
      const TranslateModule = {
        translate: ['value', customTranslate(this.translations || translationsCN)]
      }
      console.log(1111, TranslateModule)
      Modules.push(TranslateModule)

      return Modules
    },
    /**
     * 自定义模块
     */
    moddleExtensions() {
      const Extensions = {}
      // 仅使用用户自定义模块
      if (this.onlyCustomizeModdle) {
        return this.moddleExtension || null
      }

      // 插入用户自定义模块
      if (this.moddleExtension) {
        for (const key in this.moddleExtension) {
          Extensions[key] = this.moddleExtension[key]
        }
      }

      // 根据需要的 "流程类型" 设置 对应的解析文件
      Extensions.activiti = activitiModdleDescriptor
      if (this.prefix === 'activiti') {
        // Extensions.activiti = activitiModdleDescriptor
      }
      if (this.prefix === 'flowable') {
        // Extensions.flowable = flowableModdleDescriptor
      }//
      if (this.prefix === 'camunda') {
        // Extensions.camunda = camundaModdleDescriptor
      }

      return Extensions
    }
  },
  watch: {
    activeName: function(val) {
      if (val === 'xml') {
        this.previewProcessXML()
      }
      if (val === 'json') {
        this.previewProcessJson()
      }
    }
  },
  mounted() {
    this.initBpmnModeler()
    // this.createNewDiagram(null)// 后期 父组件传递
    this.$once('hook:beforeDestroy', () => {
      if (this.bpmnModeler) this.bpmnModeler.destroy()
      this.$emit('destroy', this.bpmnModeler)
      this.bpmnModeler = null
    })
    this.processRestart()
  },
  created() {
  },
  methods: {
    initBpmnModeler() {
      if (this.bpmnModeler) return
      this.bpmnModeler = new BpmnModeler({
        container: this.$refs['bpmn-canvas'],
        additionalModules: this.additionalModules,
        moddleExtensions: this.moddleExtensions
      })
      this.$emit('init-finished', this.bpmnModeler)
      this.initModelListeners()
    },
    initModelListeners() {
      const EventBus = this.bpmnModeler.get('eventBus')
      const that = this
      // 注册需要的监听事件, 将. 替换为 - , 避免解析异常
      console.log(this.events)
      this.events.forEach(event => {
        EventBus.on(event, function(eventObj) {
          const eventName = event.replace(/\./g, '-')
          const element = eventObj ? eventObj.element : null
          that.$emit(eventName, element, eventObj)
        })
      })
      // 监听图形改变返回xml
      EventBus.on('commandStack.changed', async event => {
        try {
          this.recoverable = this.bpmnModeler.get('commandStack').canRedo()
          this.revocable = this.bpmnModeler.get('commandStack').canUndo()
          const { xml } = await this.bpmnModeler.saveXML({ format: true })
          this.$emit('commandStack-changed', event)
          this.$emit('input', xml)
          this.$emit('change', xml)
        } catch (e) {
          console.error(`[Process Designer Warn]: ${e.message || e}`)
        }
      })
      // 监听视图缩放变化
      this.bpmnModeler.on('canvas.viewbox.changed', ({ viewbox }) => {
        this.$emit('canvas-viewbox-changed', { viewbox })
        const { scale } = viewbox
        this.defaultZoom = Math.floor(scale * 100) / 100
      })
    },

    setDiagramContent(data) {
      this.workFlowDetails = data
      // content
      this.processId = data.code
      this.processName = data.name
      this.processVersion = data.version
      var xml = null
      if (data.content) {
        xml = data.content
      }
      this.createNewDiagram(xml)
    },
    /**
     * 创建新的流程图
     */
    async createNewDiagram(xml) {
      // 将字符串转换成图显示出来
      const newId = this.processId || `Process_${new Date().getTime()}`
      const newName = this.processName || `业务流程_${new Date().getTime()}`
      const version = this.processVersion || `1`

      const xmlString = xml || DefaultEmptyXML(newId, newName, version, this.prefix)
      try {
        const { warnings } = await this.bpmnModeler.importXML(xmlString)
        if (warnings && warnings.length) {
          warnings.forEach(warn => console.warn(warn))
        }
      } catch (e) {
        console.error(`[Process Designer Warn]: ${e.message || e}`)
      }
    },
    /**
     * 打开文件
     */
    handleOpenFile(file) {
      const reader = new FileReader()
      let data = ''
      reader.readAsText(file.raw)
      reader.onload = (event) => {
        data = event.target.result
        this.bpmnModeler.importXML(data, (err) => {
          if (err) {
            this.msgWarning('导入失败')
          } else {
            this.msgSuccess('导入成功，请手动修改右侧属性面板ID、名称、版本属性与主流程保持一致！')
          }
        })
      }
    },
    /**
     * 下载XML
     */
    downloadProcessAsXml() {
      this.downloadProcess('xml')
    },
    /**
     * 下载SVG
     */
    downloadProcessAsSvg() {
      this.downloadProcess('svg')
    },
    /**
     * 下载BMPN
     */
    downloadProcessAsBpmn() {
      this.downloadProcess('bpmn')
    },
    /**
     * 模拟
     */
    processSimulation() {

    },
    /**
     * 放大视图
     */
    processZoomIn(zoomStep = 0.1) {
      const newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100
      if (newZoom > 4) {
        this.msgWarning('放大比例不能再放大了!')
        return
      }
      this.defaultZoom = newZoom
      this.bpmnModeler.get('canvas').zoom(this.defaultZoom)
    },
    /**
     * 缩小视图
     */
    processZoomOut(zoomStep = 0.1) {
      const newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100
      if (newZoom < 0.2) {
        this.msgWarning('缩小比例不能再缩小了!')
        return
      }
      this.defaultZoom = newZoom
      this.bpmnModeler.get('canvas').zoom(this.defaultZoom)
    },
    processZoomTo(newZoom = 1) {
      if (newZoom < 0.2) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
      }
      if (newZoom > 4) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
      }
      this.defaultZoom = newZoom
      this.bpmnModeler.get('canvas').zoom(newZoom)
    },
    /**
     * 重置视图并居中
     */
    processReZoom() {
      this.defaultZoom = 1
      this.bpmnModeler.get('canvas').zoom('fit-viewport', 'auto')
    },
    /**
     * 预览XML
     */
    previewProcessXML() {
      this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
        this.previewXMLResult = hljs.highlightAuto(xml).value
      })
    },
    /**
     * 预览JSON
     */
    previewProcessJson() {
      this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
        this.previewJSONResult = JSON.parse(convert.xml2json(xml, { spaces: 2 }))
      })
    },
    /**
     * 恢复
     */
    processRedo() {
      this.bpmnModeler.get('commandStack').redo()
    },
    /**
     * 撤销
     */
    processUndo() {
      this.bpmnModeler.get('commandStack').undo()
    },
    /**
     * 重绘
     */
    processRestart() {
      this.recoverable = false
      this.revocable = false
      this.createNewDiagram(null).then(() => this.bpmnModeler.get('canvas').zoom(1, 'auto'))
    },
    /**
     * 对其方式
     */
    elementsAlign(align) {
      const Align = this.bpmnModeler.get('alignElements')
      const Selection = this.bpmnModeler.get('selection')
      const SelectedElements = Selection.get()
      if (!SelectedElements || SelectedElements.length <= 1) {
        this.msgWarning('请按住 Ctrl 键选择多个元素对齐')
        return
      }
      this.$confirm('自动对齐可能造成图形变形，是否继续？', '警告', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => Align.trigger(SelectedElements, align))
    },
    /**
     * 保存
     */
    processSave() {
      if (!this.workFlowDetails.name) {
        this.msgWarning('请设置流程实例名称')
        return
      }
      this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
        this.$store.dispatch('workflowSaveContent', { id: this.workFlowDetails.id, content: xml }).then((res) => {
          this.msgSuccess('保存成功!')
          setTimeout(() => {
            this.$emit('notify', true)
          }, 1000)
        }).catch((_) => {})
      })
    },

    // 下载流程图到本地
    async downloadProcess(type, name) {
      try {
        const _this = this
        if (type === 'xml' || type === 'bpmn') {
          const { err, xml } = await this.bpmnModeler.saveXML()
          if (err) {
            console.error(`[Process Designer Warn ]: ${err.message || err}`)
          }
          const { href, filename } = _this.setEncoded(type.toUpperCase(), name, xml)
          downloadFunc(href, filename)
        } else {
          const { err, svg } = await this.bpmnModeler.saveSVG()
          if (err) {
            return console.error(err)
          }
          const { href, filename } = _this.setEncoded('SVG', name, svg)
          downloadFunc(href, filename)
        }
      } catch (e) {
        this.$message.warning(`[Process Designer Warn ]: ${e.message || e}`)
        console.error(`[Process Designer Warn ]: ${e.message || e}`)
      }
      // 文件下载方法
      function downloadFunc(href, filename) {
        if (href && filename) {
          const a = document.createElement('a')
          a.download = filename // 指定下载的文件名
          a.href = href //  URL对象
          a.click() // 模拟点击
          URL.revokeObjectURL(a.href) // 释放URL 对象
        }
      }
    },
    // 根据所需类型进行转码并返回下载地址
    setEncoded(type, filename = 'diagram', data) {
      const encodedData = encodeURIComponent(data)
      return {
        filename: `${filename}.${type}`,
        href: `data:application/${type === 'svg' ? 'text/xml' : 'bpmn20-xml'};charset=UTF-8,${encodedData}`,
        data: data
      }
    }
  }

}
</script>
<style lang="scss">
.djs-palette{
  top:50px !important;
  background-color: #fff;
  border: 0;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.djs-palette.two-column.open {
  width: 40px;
}
.bjs-powered-by{
  display: none
}
.djs-palette .entry, .djs-palette .djs-palette-toggle{
  width: 40px;
  font-size: 20px;
  height: 40px;
  line-height: 40px;
 cursor: pointer;
}
.custom-process-tabs{
    width: 100%;
    height: 40px;
    margin-top: 10px;
    box-shadow: none;
    border-left: 0px;
    border-bottom: 0px;
    border-right: 0px;
    .el-tabs__header{
        margin: 0px;
    }
    .el-tabs__content{
      height: 0px !important;
      padding: 0px !important;
    }
    .el-tabs--card>.el-tabs__header .el-tabs__nav{
        border-radius:0px ;
    }
    .el-tabs--border-card>.el-tabs__header {
        background-color: #f5f5f5;
        border-bottom: 0px;
        margin: 0;
    }
}
</style>
<style lang="scss" scoped>
.custom-process-designer{
    height: 100%;
    background-color: #fff;
    display: flex;
    flex-direction: column;
    .custom-process-designer__header{
        width: 100%;
        min-height: 50px;
        padding: 10px;
        z-index: 1000;
        border-bottom: 1px solid #EFEFEF;
    }
    .custom-process-designer__container {
        height: calc(100% - 50px);
        flex: 1;
        display: flex;
        flex-direction: column;
        .custom-process-designer__canvas{
            flex: 1;
            height: 100%;
            background: url() repeat !important;
        }
        .custom-process-panel{
            width: 350px;
            padding: 0 10px;
        }
    }
}
</style>
